﻿/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.

THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data.Entity.Design;
using System.Data.Metadata.Edm;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using EntityFrameworkContrib.Design.VisualStudio;

namespace EntityFrameworkContrib.Design.VisualStudio
{
	/// <summary>
	/// This is the generator class. 
	/// When the 'Custom Tool' property of a .edmx file in a C# or VB project item is set to "PluginEdmxCodeGenerator", 
	/// Visual Studio will call the GenerateCode function which will return the contents of the generated file back
	/// to the project system
	/// </summary>
	[ComVisible( true ), Guid( "6B0C2FD3-9355-4183-9666-D86D2BA1D6BB" )]
	public class PluginEdmxCodeGenerator : BaseCodeGeneratorWithSite
	{
		readonly static XNamespace 
			edmxns = "http://schemas.microsoft.com/ado/2007/06/edmx",
			edmns = "http://schemas.microsoft.com/ado/2006/04/edm";

		/// <summary>
		/// Make the generated (a.k.a. code-behind) file have the file extension ".Designer.cs" or ".Designer.vb"
		/// </summary>
		/// <returns></returns>
		protected override string GetDefaultExtension()
		{
			return string.Concat( ".Designer", base.GetDefaultExtension() );
		}

		/// <summary>
		/// Override to generate and return code from the contents of the input file contents
		/// Does the following:
		/// - extracts CSDL content from the input EDMX content
		/// - calls public code generation APIs in System.Data.Entity.Design with the appropriate language
		/// - hooks code generation events to add custom CLR attributes to the gerenated code
		/// - (the attributes to add are present as CSDL annotations in the EDMX file)
		/// - uses EDM metadata APIs to find CSDL annotations in the EDMX file and turns them into CLR attributes
		/// - logs warnings & errors to the Visual Studio error window
		/// - returns the generated code as a byte array to Visual Studio
		/// </summary>
		/// <param name="inputFileContent">Content of the input EDMX file</param>
		/// <returns>Generated source code (C# or VB) as a byte array</returns>
		protected override byte[] GenerateCode( string inputFileContent )
		{
			try
			{
				// Check for and get CSDL content
				XElement csdlContent = ExtractCsdlContent( inputFileContent );

				// Reader over CSDL content within EDMX
				using ( XmlReader csdlReader = csdlContent.CreateReader() )
				{
					using ( StringWriter codeWriter = new StringWriter( CultureInfo.InvariantCulture ) )
					{
						// Determine language for generated code
						LanguageOption languageOption = ResolveLanguageOption();
						
						UpdateProgress( .33f );

						// Create code generator and wire up events
						EntityClassGenerator generator = new EntityClassGenerator( languageOption );
						PluginManager.Broadcast( generator );
						UpdateProgress( .66f );



						IList<EdmSchemaError> errors = generator.GenerateCode( csdlReader, codeWriter );
						if ( errors != null )
						{
							// Generate code and log warnings/errors to the Visual Studio error window
							foreach ( EdmSchemaError error in errors )
							{
								uint line = (uint)Math.Max( 0, error.Line - 1 ),
									column = (uint)Math.Max( 0, error.Column - 1 );

								if ( error.Severity == EdmSchemaErrorSeverity.Warning )
								{
									GeneratorWarning( 0, error.Message, line, column );
								}
								else
								{
									GeneratorError( 4, error.Message, line, column );
								}
							}
						}

						UpdateProgress( 1 );

						// Convert generated code into bytes to return to Visual Studio
						return Encoding.UTF8.GetBytes( codeWriter.ToString() );
					}
				}
			}
			catch ( Exception e )
			{
				GeneratorError( 4, e.Message, 1, 1 );

				// Returning null signifies that generation has failed
				return null;
			}
		}

		PluginManager PluginManager
		{
			get { return pluginManager ?? ( pluginManager = new PluginManager( GetProject() ) ); }
		}	PluginManager pluginManager;

		void UpdateProgress( float percentage )
		{
			if ( CodeGeneratorProgress != null )
			{
				// Report that we are 100% done
				CodeGeneratorProgress.Progress( (uint)( 100 * percentage ), 100 );
			}
		}

		string ResolveExtension()
		{
			string extension = GetCodeProvider().FileExtension,
				result = !string.IsNullOrEmpty( extension ) ? string.Concat( '.' , extension.TrimStart( '.' ) ) : extension;
			return result;
		}

		LanguageOption ResolveLanguageOption()
		{
			string extension = ResolveExtension();
			if ( extension.EndsWith( ".vb", StringComparison.InvariantCultureIgnoreCase ) )
			{
				return LanguageOption.GenerateVBCode;
			}
			else if ( extension.EndsWith( ".cs", StringComparison.InvariantCultureIgnoreCase ) )
			{
				return LanguageOption.GenerateCSharpCode;
			}
			throw new InvalidOperationException( "Unsupported project language. Only C# and VB are supported." );
		}

		/// <summary>
		/// Extracts CSDL content from the EDMX file content and returns it as an XElement
		/// </summary>
		/// <param name="inputFileContent">EDMX file conmtent</param>
		/// <returns>CSDL content XElement</returns>
		static XElement ExtractCsdlContent( string inputFileContent )
		{
			XDocument edmxDoc = XDocument.Load( new StringReader( inputFileContent ) );
			if ( edmxDoc != null )
			{
				XElement edmxNode = edmxDoc.Element( edmxns + "Edmx" );
				if ( edmxNode != null )
				{
					XElement runtimeNode = edmxNode.Element( edmxns + "Runtime" );
					if ( runtimeNode != null )
					{
						XElement conceptualModelsNode = runtimeNode.Element( edmxns + "ConceptualModels" );
						if ( conceptualModelsNode != null )
						{
							return conceptualModelsNode.Element( edmns + "Schema" );
						}
					}
				}
			}
			throw new InvalidOperationException( "No CSDL content in input file" );
		}
	}
}